﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
//using System.Drawing; (Do we need this??)
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using WIN_UML_Editor;
using UMLObjects;
using UMLUserControls;
using System.Windows.Media;


namespace Commands
{
    
    public class UMLCanvas : Canvas, IUMLCanvas
    {
        private UndoService US;
        private ActivityDiagram mainDiagram;
        private UIElement _dragElement;
        private Point _originalElementPos, _dragStart;       
        private IArrowedUC _createArrowFromThis;
        private ArrowGuideLine _arrowguide;

        public UMLCanvas(UndoService US, ActivityDiagram MainDiagram)
        {
            this.mainDiagram = MainDiagram;
            this.US = US;
            this.MouseLeftButtonDown += canvasMouseButtonDown;
            this.MouseLeftButtonUp += canvasMouseButtonUp;
            this.MouseLeave += canvasMouseCursorLeave;
            this.MouseMove += canvasMouseCursorMove;
        }

        public ActivityDiagram MainDiagram
        {
            get { return mainDiagram; }
            set { mainDiagram = value; }
        }
        
        //Add a new UI element to the surface
        public void AddElement<T>(T element)
        {
            UIElement uiElement = element as UIElement;
            if (uiElement != null && !Children.Contains(uiElement))
                Children.Add(uiElement);

            //Check type and add
            if (element is UCActivity)
            {
                UCActivity UCact = element as UCActivity;
                IUMLObject act = UCact.getObject();
                mainDiagram.Activities.Add((Activity)act); //Add to maindiagram
                UMLFocus.Instance.Object = UCact;
            }
            if (element is UCInitialState)
            {
                UCInitialState UCstate = element as UCInitialState;
                IUMLObject state = UCstate.getObject();
                mainDiagram.InitialStates.Add((InitialState)state); //Add to maindiagram
                UMLFocus.Instance.Object = UCstate;
            }
            if (element is UCDecision)
            {
                UCDecision UCdec = element as UCDecision;
                IUMLObject decision = UCdec.getObject();
                mainDiagram.Decisions.Add((Decision)decision); //Add to maindiagram
                UMLFocus.Instance.Object = UCdec;
            }
            if (element is UCArrow)
            {
                UCArrow UCarr = element as UCArrow;
                UCarr.PointingTo.AddArrowTo(UCarr); //Inform target of arrow
                UCarr.PointingFrom.AddArrowFrom(UCarr); //Inform sender of arrow
                IUMLObject arrow = UCarr.getObject();
                mainDiagram.Arrows.Add((Arrow)arrow);
            }
            if (element is UCFinalState)
            {
                UCFinalState UCstate = element as UCFinalState;
                IUMLObject state = UCstate.getObject();
                mainDiagram.FinalStates.Add((FinalState)state); //Add to maindiagram
                UMLFocus.Instance.Object = UCstate;
            }


            //Add attached arrows
            if (element is IArrowedUC)
            {
                List<UCArrow> arrowsUCto = new List<UCArrow>(((IArrowedUC)element).ArrowsToThis);
                foreach (UCArrow arrowUC in arrowsUCto)
                {
                    Children.Add(arrowUC);
                    mainDiagram.Arrows.Add((Arrow)arrowUC.getObject());
                    arrowUC.PointingFrom.AddArrowFrom(arrowUC);
                }

                List<UCArrow> arrowsUCfrom = new List<UCArrow>(((IArrowedUC)element).ArrowsFromThis);
                foreach (UCArrow arrowUC in arrowsUCfrom)
                {
                    Children.Add(arrowUC);
                    mainDiagram.Arrows.Add((Arrow)arrowUC.getObject());
                    arrowUC.PointingTo.AddArrowTo(arrowUC);
                }
            }

        }

        //Remove UI element from surface
        public void RemoveElement<T>(T element)
        {
            UIElement uiElement = element as UIElement;
            if (uiElement != null && Children.Contains(uiElement))
            {
                Children.Remove(uiElement);

                //Check type and delete
                if (element is UCActivity)
                {
                    UCActivity UCact = element as UCActivity;
                    IUMLObject act = UCact.getObject();
                    mainDiagram.Activities.Remove((Activity)act); //remove from this maindiagram
                }
                if (element is UCInitialState)
                {
                    UCInitialState UCstate = element as UCInitialState;
                    IUMLObject state = UCstate.getObject();
                    mainDiagram.InitialStates.Remove((InitialState)state); //remove from this maindiagram
                }
                if (element is UCDecision)
                {
                    UCDecision UCdec = element as UCDecision;
                    IUMLObject decision = UCdec.getObject();
                    mainDiagram.Decisions.Remove((Decision)decision); //remove from this maindiagram
                }
                if (element is UCArrow)
                {
                    UCArrow UCarr = element as UCArrow;
                    UCarr.PointingTo.RemoveArrowTo(UCarr);
                    UCarr.PointingFrom.RemoveArrowFrom(UCarr);
                    IUMLObject arrow = UCarr.getObject();
                    mainDiagram.Arrows.Remove((Arrow)arrow); //remove from this maindiagram
                }
                if (element is UCFinalState)
                {
                    UCFinalState UCstate = element as UCFinalState;
                    IUMLObject state = UCstate.getObject();
                    mainDiagram.FinalStates.Remove((FinalState)state); //remove from this maindiagram
                }


                //Remove attached arrows
                if (element is IArrowedUC)
                {
                    List<UCArrow> arrowsUCto = new List<UCArrow>(((IArrowedUC)element).ArrowsToThis);
                    foreach (UCArrow arrowUC in arrowsUCto)
                    {
                        Children.Remove(arrowUC);
                        mainDiagram.Arrows.Remove((Arrow)arrowUC.getObject());
                        arrowUC.PointingFrom.RemoveArrowFrom(arrowUC);
                    }

                    List<UCArrow> arrowsUCfrom = new List<UCArrow>(((IArrowedUC)element).ArrowsFromThis);
                    foreach (UCArrow arrowUC in arrowsUCfrom)
                    {
                        Children.Remove(arrowUC);
                        mainDiagram.Arrows.Remove((Arrow)arrowUC.getObject());
                        arrowUC.PointingTo.RemoveArrowTo(arrowUC);
                    }
                }
            }

            UMLFocus.Instance.Object = null; //remove focus
        }

        //Place a UI element at a given position.
        public void DropElement<T>(T element, Point position)
        {
            UIElement uiElement = element as UIElement;
            if (uiElement != null && Children.Contains(uiElement))
            {
                Canvas.SetLeft(uiElement, position.X);
                Canvas.SetTop(uiElement, position.Y);
            }
            
            //Update internal object position
            IDragableUC uc = element as IDragableUC;
            if (uc != null)
            {
                uc.ObjPosition = position;
            }

            //Update arrows if any
            if (element is IArrowedUC)
            {
                ((IArrowedUC)element).UpdateArrows();
            }
        }

        //Element operations

        public void addAcitivityUC(UCActivity uc)
        {
            AddActivityCommand cmd = new AddActivityCommand(this, uc);
            US.Execute(cmd);

        }

        public void addInitialStateUC(UCInitialState uc)
        {
            AddInitialStateCommand cmd = new AddInitialStateCommand(this, uc);
            US.Execute(cmd);

        }

        public void addDecisionUC(UCDecision uc)
        {
            AddDecisionCommand cmd = new AddDecisionCommand(this, uc);
            US.Execute(cmd);

        }

        public void addFinalStateUC(UCFinalState uc)
        {
            AddFinalStateCommand cmd = new AddFinalStateCommand(this, uc);
            US.Execute(cmd);

        }

        public void dropUMLUC(IDragableUC uc,Point position,Point preposition)
        {
            UIElement element = uc as UIElement;
            DropElementCommand cmd = new DropElementCommand(this, element, position, preposition);
            US.Execute(cmd);
        }

        //Events (Drag'N'Drop):

        private void canvasMouseButtonDown(object sender, MouseButtonEventArgs e)
        {
            
            // Get the coordinates of the mouse position
            Point mousePosition = e.GetPosition(this);

            // Tests if any objects were hit
            HitTestResult elementHit = VisualTreeHelper.HitTest(this, mousePosition);

            //Extract any element
            var element = getTopParent(elementHit);

            if (element != null)
            {
                //Select object
                if (element is IUMLUC && _createArrowFromThis==null)
                    UMLFocus.Instance.Object = (IUMLUC)element;

                //Drag n drop
                if (element is IDragableUC && _createArrowFromThis == null)
                {
                    _dragElement = (UIElement)element;

                    // Get the position of the element
                    _originalElementPos = new Point()
                    {
                        X = Canvas.GetLeft(_dragElement),
                        Y = Canvas.GetTop(_dragElement)
                    };

                    // Gets position of the mouse
                    _dragStart = mousePosition;
                }

                //Create Arrow
                if (_createArrowFromThis != null && element is IArrowedUC)
                {
                    //Make sure the element to where the arrow will go is not already pointing on this object
                    int temp = ((IArrowedUC)element).ArrowsFromThis.Count;
                    Boolean overlap = true;
                    for (int i = 0; i < temp; i++)
                    {
                        if (((IArrowedUC)element).ArrowsFromThis[i].PointingTo == _createArrowFromThis)
                            overlap = false;
                    }
                                        
                    IArrowedUC uc = element as IArrowedUC;
                    if (uc.canReceiveArrow() && _createArrowFromThis != uc && overlap)
                    {
                        Arrow arrow = new Arrow(); //Create arrow object
                        arrow.PointsFrom = _createArrowFromThis.getObject(); //set from object
                        arrow.PointsTo = uc.getObject(); //Set to object
                        UCArrow arrowUC = new UCArrow(arrow, uc, _createArrowFromThis); //create UC

                        //Fire undoable command.
                        AddArrowCommand cmd = new AddArrowCommand(this, arrowUC);
                        US.Execute(cmd);

                        //disable create-mode
                        _createArrowFromThis = null;
                        //remove guide
                        Children.Remove(_arrowguide);
                        _arrowguide = null;
                    }
                }
            }
            else
            {      
                UMLFocus.Instance.Object = null;

                //Stop create arrow, if running
                _createArrowFromThis = null;

                //remove guide
                Children.Remove(_arrowguide);
                _arrowguide = null;
            }
        }

        private DependencyObject getTopParent(HitTestResult eH)
        {
            var element = eH.VisualHit;
            while (element != null && !(element is IUMLUC)) //Get top parent
                element = VisualTreeHelper.GetParent(element);
            return element; //return the parent
        }

        private void canvasMouseButtonUp(object sender, MouseButtonEventArgs e)
        {
            //If any selected object: drop it.
            if (_dragElement != null)
            {
                IDragableUC currentUC = _dragElement as IDragableUC;
                currentUC.ObjPosition = new Point(Canvas.GetLeft(_dragElement), Canvas.GetTop(_dragElement));

                //Fire undo/redo-able command
                if (currentUC.ObjPosition != _originalElementPos)
                    dropUMLUC(currentUC, currentUC.ObjPosition, _originalElementPos);

                _dragElement = null;
            }
        }

        private void canvasMouseCursorLeave(object sender, MouseEventArgs e)
        {
            //If cursor leaves object: drop it
            if (_dragElement != null)
            {
                IDragableUC currentUC = _dragElement as IDragableUC;
                currentUC.ObjPosition = new Point(Canvas.GetLeft(_dragElement), Canvas.GetTop(_dragElement));

                //Fire undo/redo-able command
                dropUMLUC(currentUC, currentUC.ObjPosition, _originalElementPos);

                _dragElement = null;
            }
        }

        private void canvasMouseCursorMove(object sender, MouseEventArgs e)
        {
            Point mousePosition = e.GetPosition(this);

            // If an object is being dragged
            if (_dragElement != null)
            {
                IDragableUC currentUC = _dragElement as IDragableUC;
                currentUC.ObjPrePosition = new Point(Canvas.GetLeft(_dragElement), Canvas.GetTop(_dragElement));

                // Calculates the dragged element's new position, with grid set to 10px
                Point newElementPosition = new Point()
                {
                    X = System.Math.Round((_originalElementPos.X + (mousePosition.X - _dragStart.X)) * 0.2)*5,
                    Y = System.Math.Round((_originalElementPos.Y + (mousePosition.Y - _dragStart.Y)) * 0.2)*5
                };


                // Redraws the element at this position IF its inside the canvas!
                UserControl currentUIElement = currentUC as UserControl;
                if (newElementPosition.X+currentUIElement.ActualWidth < this.ActualWidth && newElementPosition.X >= 0)
                    Canvas.SetLeft(_dragElement, newElementPosition.X);

                if (newElementPosition.Y + currentUIElement.ActualHeight < this.ActualHeight && newElementPosition.Y >= 0)
                    Canvas.SetTop(_dragElement, newElementPosition.Y);
                
                //Update the object
                currentUC.ObjPosition = new Point(Canvas.GetLeft(_dragElement), Canvas.GetTop(_dragElement));

                //Update arrow if any.
                if (currentUC is IArrowedUC)
                {
                    IArrowedUC arrowUC = currentUC as IArrowedUC;
                    arrowUC.UpdateArrows();
                }
            }

            //Move the guide
            if (_arrowguide != null)
            {
                FrameworkElement startelement = UMLFocus.Instance.Object as FrameworkElement;
                Point guidestart = new Point(startelement.ActualWidth*0.5+GetLeft(startelement),startelement.ActualHeight*0.5+GetTop(startelement));
                
                //Test if compatible element is below cursor
                HitTestResult overelementHit = VisualTreeHelper.HitTest(this, mousePosition);
                var overelement = getTopParent(overelementHit);

                //Sticky
                if (overelement is IArrowedUC && ((IArrowedUC)overelement).canReceiveArrow())
                {
                    //Make sure the element to where the arrow will go is not already pointing on this object
                    int temp = ((IArrowedUC)overelement).ArrowsFromThis.Count;
                    Boolean overlap = true;
                    for(int i=0; i<temp; i++)
                    {
                        if(((IArrowedUC)overelement).ArrowsFromThis[i].PointingTo == _createArrowFromThis)
                            overlap = false;
                    }

                    if(overlap)
                    {
                        double stickywidth = ((FrameworkElement)overelement).ActualWidth;
                        double stickyheight = ((FrameworkElement)overelement).ActualHeight;
                        Point stickypos = new Point(GetLeft((UIElement)overelement)+stickywidth*0.5, GetTop((UIElement)overelement)+stickyheight*0.5);
                        _arrowguide.Update(guidestart, stickypos,true);
                    }
                }
                else //follow cursor
                    _arrowguide.Update(guidestart, mousePosition,false);
            }
        }

        public void ActivateArrowCreator(IArrowedUC from)
        {
            _createArrowFromThis = from;
            _arrowguide = new ArrowGuideLine();
            Children.Add(_arrowguide);
        }
    }
}
